-
Notifications
You must be signed in to change notification settings - Fork 140
CBG-3689: CheckProposedRev does not unmarshall the entire history #7875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR optimizes CheckProposedRev by introducing a new unmarshal level (DocUnmarshalRevAndFlags) that only unmarshals the current revision and flags, avoiding the expensive operation of unmarshalling the entire document history when checking if a document is deleted.
Key Changes:
- Added
DocUnmarshalRevAndFlagsconstant for selective unmarshalling - Moved
IsDeleted()andhasFlag()methods fromDocumenttoSyncDatastruct - Simplified
CheckProposedRevto use the new unmarshal level instead of conditionally loading full history
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| db/document.go | Added new unmarshal level and moved deletion checking methods to SyncData |
| db/crud.go | Simplified CheckProposedRev to use the new unmarshal level |
| db/crud_test.go | Added comprehensive unit tests for CheckProposedRev functionality |
db/document.go
Outdated
| var revOnlyMeta revAndFlagsSyncData | ||
| unmarshalErr := base.JSONUnmarshal(syncXattrData, &revOnlyMeta) | ||
| if unmarshalErr != nil { | ||
| return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRev). Error: %v", base.UD(doc.ID), unmarshalErr)) |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message references 'DocUnmarshalRev' but this case is for 'DocUnmarshalRevAndFlags'. Update the error message to accurately reflect the unmarshal level being used.
| return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRev). Error: %v", base.UD(doc.ID), unmarshalErr)) | |
| return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRevAndFlags). Error: %v", base.UD(doc.ID), unmarshalErr)) |
db/document.go
Outdated
| } | ||
| doc._rawBody = data | ||
| case DocUnmarshalRevAndFlags: | ||
| // Unmarshal rev ,cas and flags from sync metadata |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected spacing after 'rev' in comment.
| // Unmarshal rev ,cas and flags from sync metadata | |
| // Unmarshal rev, cas and flags from sync metadata |
db/crud.go
Outdated
| // Proposed rev's parent is my current revision; OK to add: | ||
| return ProposedRev_OK, "" | ||
| } else if parentRevID == "" && syncData.History[syncData.GetRevTreeID()].Deleted { | ||
| } else if parentRevID == "" && syncData.hasFlag(channels.Deleted) { |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hasFlag method is not exported and is being called on SyncData. Based on the code changes, IsDeleted() method was added to SyncData which should be used instead for better encapsulation and consistency.
| } else if parentRevID == "" && syncData.hasFlag(channels.Deleted) { | |
| } else if parentRevID == "" && syncData.IsDeleted() { |
torcolvin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM in general, I have left some more comments to pick up on go, Sync Gateway style, and the level of testing I prefer. Some of this is idealism but I'd rather note it here when it is easy to achieve and we can discuss how much to do on future tickets when this is harder to achieve.
db/crud_test.go
Outdated
|
|
||
| func TestProposedRev(t *testing.T) { | ||
|
|
||
| base.SetUpTestLogging(t, base.LevelDebug, base.KeyAll) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpicks:
I think this line is useful for debugging, but LevelDebug with KeyAll displays a lot of logging for rosmar that isn't helpful. When committing this, I think it's fine for this test to remove all the logging.
| defer db.Close(ctx) | ||
| collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) | ||
|
|
||
| // create 3 documents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
To make this easier to read and debug in the future I think I would do something like name these documents with what they are:
const (
singleRevDoc = "singleRevDoc"
twoRevDoc = "twoRevDoc"
tombstonedDoc = "tombstonedDoc"
)
db/crud_test.go
Outdated
| revID string | ||
| parentRevID string | ||
| expectedStatus ProposedRevStatus | ||
| currentRev string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| currentRev string | |
| expectedCurrentRev string |
This indicates it is the return value of the struct
db/crud_test.go
Outdated
| docID string | ||
| }{ | ||
| { | ||
| name: "no existing document", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General comment, but I think this is worth addressing for this PR.
When writing a test, you want to think about the way the code is called, not just the implementation. Some of the idea of testing is to cover the cases that don't have different code pathways today but do have different code pathways in the future.
So there are several to account for and in a fast test it would be great to hit all of the possibilities
- type of document: (no document, multiple rev, tombstone)
- arguments (revID, parentRevID)
- revID possibilities:
- revID doesn't exist
- revID does exist and is current rev
- revID does exist and is in history
- parentRevID
- parentRevID doesn't exist
- parentRevID does exist, and is the immediate parent
- parentRevID does exist in the history but isn't the immediate parent. (In this case, you can test this by having the documents have three revisions so you can test for a past parent)
I'm not going to list all these permutations, but given how fast this test runs I think it would be fine to hit most of them. I think this is 3 * 3 * 3 which is a manageable number. If it was too many, then I think consider skip more, but this gives more coverage to this function. If this does really blow up because I did the math wrong, we definitely can consider shortening the number of cases.
the first example that strikes me is what happens when you have:
- no existing document
- rev: 2-def
- parentRev: 1-def
That's a common scenario where Couchbase Lite is pushing a document that has two local revisions and Sync Gateway has none.
| CurrentRev channels.RevAndVersion `json:"rev"` | ||
| } | ||
|
|
||
| type revAndFlagsSyncData struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| type revAndFlagsSyncData struct { | |
| // revAndFlagsSyncData is a limited set of the SyncData suitable for faster unmarshalling. It only contains rev and flags properties | |
| type revAndFlagsSyncData struct { |
| var revOnlyMeta revAndFlagsSyncData | ||
| unmarshalErr := base.JSONUnmarshal(syncXattrData, &revOnlyMeta) | ||
| if unmarshalErr != nil { | ||
| return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRevAndFlags). Error: %v", base.UD(doc.ID), unmarshalErr)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: general go style
pkgerrors WithStack was used before go implemented its own error wrapping in go 1.13. Some of Sync Gateway's code base was written before this existed. Preferred way of doing this is to use %w on base.RedactErrorf or fmt.Errorf to use "native" go error wrapping.
| return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRevAndFlags). Error: %v", base.UD(doc.ID), unmarshalErr)) | |
| return base.RedactErrorf("Failed to UnmarshalWithXattrs() doc with id: %s (DocUnmarshalRevAndFlags). Error: %w", base.UD(doc.ID), unmarshalErr) |
CBG-3689
Describe your PR here...
CheckProposedRevdoes not unmarshall the entire history to check if the current version is deleted or notPre-review checklist
fmt.Print,log.Print, ...)base.UD(docID),base.MD(dbName))docs/apiDependencies (if applicable)
Integration Tests
GSI=true,xattrs=truehttps://jenkins.sgwdev.com/job/SyncGatewayIntegration/162/